const simEditor = (function () {
  /**
   * 处理函数
   * @callback ReplacerCallback
   * @param {{src: string,alt:string}} e
   */
  /**
   * 表情替换
   * @param {string} content 文本内容
   * @param {Object} srcMap  表情映射
   * @param {ReplacerCallback} replacer 处理函数
   * @returns
   */
  function replaceEmotion(
    content,
    srcMap,
    replacer = (e) => {
      return e;
    }
  ) {
    const pattern = /\[[\u4e00-\u9fa5_a-zA-Z]+\]/g;
    let emotions = content.match(pattern);
    emotions = Array.from(new Set(emotions));
    let str = content;
    str = str.replace(pattern, (s) => {
      if (emotions.indexOf(s) > -1 && srcMap[s]) {
        return replacer({ src: srcMap[s], alt: s });
      } else {
        return s;
      }
    });

    return str;
  }

  /**
   * 缓存光标
   * @param {Node} editNode 编辑节点
   */
  function editCacheRange(editNode) {
    if (window.getSelection) {
      const sel = window.getSelection();
      if (sel.getRangeAt && sel.rangeCount) {
        editNode._cache_range = sel.getRangeAt(0);
      }
    } else if (document.selection && document.selection.createRange) {
      editNode._cache_range = document.selection.createRange();
    }
  }

  // 暴力清除标签样式
  function html2text(html) {
    if (!html) {
      return '';
    }
    const enterRegExp = /<\/(p|div)>/g;
    // img标签alt的值
    const altRegExp = /<img.*?alt="(.*?)">/gi;
    const text = html
      .replace(enterRegExp, '\n')
      .replace(/\\r/g, '')
      .replace(altRegExp, '$1')
      .replace(/<[^>]+>/g, '');
    return text.trim();
  }

  /**
   * 文本渲染过滤器
   * @param {string} text 文本
   * @param {{[alt:string]:[src:string]}} txt2imgMap 文本2图片
   * @returns {string}
   */
  const mgsFilters = (text, txt2imgMap) => {
    txt2imgMap = txt2imgMap || {
      '[猫猫]': 'https://img.yzcdn.cn/vant/cat.jpeg',
    };
    return replaceEmotion(text, txt2imgMap, ({ src, alt }) => {
      return `<img src="${src}" alt="${alt}" />`;
    });
  };

  /**
   * 插入信息到编辑器
   * @param {Node} node 节点
   * @param {string} html html文本
   */
  function insertHtml(node, html) {
    const content = document.createElement('p');
    content.innerHTML = html || '<br>';
    node.innerHTML = '';
    node.appendChild(content);
  }

  /**
   * 获取粘贴文本
   * @param {Event} e 粘贴事件
   * @returns {string}
   */
  function getPasteText(e) {
    if (!(e.clipboardData && e.clipboardData.items)) {
      return;
    }
    const clipboardData = e.clipboardData; // 暂不考虑 originalEvent 的情况
    let pasteText = '';
    if (clipboardData == null) {
      pasteText = window.clipboardData && window.clipboardData.getData('text');
    } else {
      pasteText = clipboardData.getData('text/plain');
    }
    return pasteText;
  }

  /**
   * 剔除编辑区容器的 attribute 变化中的非 contenteditable 变化
   * @param {MutationRecord[]} mutations
   * @param {Node} tar 编辑区容器的 DOM 节点
   */
  function mutationsFilter(mutations, tar) {
    // 剔除编辑区容器的 attribute 变化中的非 contenteditable 变化
    return mutations.filter(({ type, target, attributeName }) => {
      return (
        type !== 'attributes' ||
        (type === 'attributes' &&
          (attributeName === 'contenteditable' || target !== tar))
      );
    });
  }

  /**
   * 节点变化监听
   * @param {Node} node 节点
   * @param {{onChange:MutationCallback}} op 选项
   * @returns
   */
  function observeNode(node, op = {}) {
    const { onChange } = op;
    // 观察器的配置（需要观察什么变动）
    const config = {
      subtree: true, // 子节点树变化
      childList: true, // 节点变化
      attributes: true, // 属性变化
      attributeOldValue: true,
      characterData: true, // 节点内容或文本变化
      characterDataOldValue: true,
    };
    // 创建一个观察器实例并传入回调函数
    const _nodeObserver = new MutationObserver((mutationsList, observer) => {
      onChange && onChange(mutationsList, observer);
    });
    // 以上述配置开始观察目标节点
    _nodeObserver.observe(node, config);
    return _nodeObserver;
  }

  /**
   * 插入元素到编辑器
   * @param {Node} editNode 编辑节点对象
   * @param {Node|string} insertNode  插入节点对象
   * @param {{enterNodeName:string,isEnter:boolean,insertType:string}} op 选项 enterNodeName：换行标签,大写，isEnter 是否是换行,insertType：插入类型text/html
   */
  function insertNodeAtCaret(editNode, insertNode, op = {}) {
    const { enterNodeName = 'p', isEnter = false } = op;
    let sel, range, inEditRange;
    if (window.getSelection) {
      sel = window.getSelection();

      if (sel.getRangeAt && sel.rangeCount) {
        range = sel.getRangeAt(0);
        inEditRange =
          range &&
          (range.endContainer === editNode ||
            editNode.contains(range.endContainer));
      }

      // 取出缓存range
      if (!inEditRange && editNode._cache_range) {
        range = editNode._cache_range;
        inEditRange =
          range &&
          (range.endContainer === editNode ||
            editNode.contains(range.endContainer));
      }

      if (!inEditRange) {
        const editRange = document.createRange();
        editRange.selectNode(editNode);
        range = editRange;
        range.selectNodeContents(editNode);
        // 编辑器有内容就光标就移到最后
        if (editNode.lastChild) {
          range.selectNodeContents(editNode.lastChild);
          range.setStart(editNode.lastChild, 1);
          range.setEnd(editNode.lastChild, 1);
          inEditRange = true;
        }
      }

      // 把选中范围清空
      range.deleteContents();

      // 是否在文本里
      const isInText = range.endContainer.nodeName === '#text';
      // 是否在换行标签里
      const isInEnter =
        inEditRange &&
        range.endContainer.nodeName === enterNodeName.toUpperCase();
      // 光标范围里的元素
      const rangeNodes = range.endContainer.childNodes;

      // 只有父节点是否是编辑对象
      const isPEditNode = range.endContainer.parentNode === editNode;

      let endContainer = range.endContainer;
      // 在文本中，需要把范围设置到P标签上
      if (!isPEditNode && isInText) {
        endContainer = range.endContainer.parentNode;
      }

      // 只是换行，换行内容存在'<br>'
      const justBr = endContainer.innerHTML === '<br>';

      // 编辑器是否空
      const isEmptyEditNode = !editNode.innerHTML;

      if (isEnter) {
        if (isEmptyEditNode) {
          // 存在空元素
          range.insertNode(insertNode);
          range.setStartAfter(insertNode);
          insertNode = insertNode.cloneNode(true);
        } else if (isInText) {
          // 在文本
          const textLen = range.endContainer.textContent.length;
          const parentNodes = range.endContainer.parentNode.childNodes;
          const parentNodeLen = parentNodes.length;
          const index = [...parentNodes].indexOf(range.endContainer);
          if (textLen <= range.endOffset && index === parentNodeLen - 1) {
            range.setStartAfter(endContainer);
          } else if (range.endOffset === 0 && index === 0) {
            range.setEndBefore(endContainer);
          } else {
            insertNode = document.createTextNode('\n');
          }
        } else if (isInEnter) {
          // 在换行标签里
          if (!justBr && range.endOffset === 0) {
            range.setEndBefore(range.endContainer);
          } else if (!justBr && rangeNodes.length > range.endOffset) {
            // 在图片标签之间的换行
            insertNode = document.createTextNode('\n');
          } else {
            // 把范围后移一个单位
            range.setStartAfter(range.endContainer);
          }
        }
      } else {
        // 净空br, 再插入
        if (justBr) {
          range.endContainer.innerHTML = '';
        }
        // 插入标签是否在块元素里
        const isBlockInsertNode =
          ['P', 'DIV'].indexOf(insertNode.nodeName) > -1;
        // 当前在块元素里
        if (isInEnter) {
          if (isBlockInsertNode) {
            // 当前空行只有br内容
            if (justBr) {
              range.endContainer.innerHTML = insertNode.innerHTML;
              range.setEnd(
                range.endContainer,
                range.endContainer.childNodes.length
              );
              insertNode = null;
            } else {
              range.setStartAfter(endContainer);
            }
          }
        } else {
          if (!isBlockInsertNode && !isInText) {
            const _p = document.createElement('p');
            _p.appendChild(insertNode);
            insertNode = _p;
          }
        }
      }

      // 插入元素
      if (insertNode) {
        range.insertNode(insertNode);
        // 换行、空行特殊处理
        if (isEnter || isEmptyEditNode) {
          range.selectNodeContents(insertNode);
        }
      }

      // 清空范围，但并不删除内容
      range.collapse(false);
      sel.removeAllRanges();
      sel.addRange(range);

      // // 存一下光标
      // edit._cache_range = range
    } else if (document.selection && document.selection.createRange) {
      // 吧啦吧啦吧啦兼容没有做
      // range = document.selection.createRange()
      // range.text = text;
    }
  }

  const funcs = {
    insertNodeAtCaret,
    observeNode,
    mutationsFilter,
    getPasteText,
    mgsFilters,
    insertHtml,
    replaceEmotion,
  };
  /**
   * 初始化编辑器
   * @param {Node} node 编辑器节点
   * @param {{
   * onChange:(txt)=>void,
   * onPaste:(e:Event)=>void|boolean,
   * textFilters:(text:string)=>string,
   * maxLength:number|undefined
   * }} op 选项，
   * onChange：变化监听，onPaste：粘贴事件，textFilters，自定义文本过滤
   * @returns {funcs&{
   * html: (text: string)=>void,
   * setMaxLength:(num:number)=>void
   * }}
   */
  function initEdit(node, op = {}) {
    // if (initEdit) {
    //     // return
    // }

    const onChange = op.onChange;
    const onPaste = op.onPaste;
    const textFilters = op.textFilters || mgsFilters;

    // 限制大小
    let maxLength = op.maxLength || undefined;
    const setMaxLength = (num) => {
      maxLength = num;
    };
    const keyIgn = [
      'Backspace',
      'ArrowUp',
      'ArrowDown',
      'ArrowLeft',
      'ArrowRight',
    ];
    const keyCodeIgn = [8, 38, 40, 37, 39];
    const _editMaxLengthEvent = (e) => {
      if (keyIgn.indexOf(e.key) > -1 || keyCodeIgn.indexOf(e.keyCode) > -1) {
        return;
      }
      const text = html2text(e.target.innerHTML);
      if (maxLength && text.length >= maxLength) {
        e.preventDefault();
      }
    };
    node.addEventListener('keyup', _editMaxLengthEvent);
    node.addEventListener('keydown', _editMaxLengthEvent);

    // 改变光标缓存
    function _editChangeCacheRange() {
      editCacheRange(node);
    }

    // 存在光标就清空，并重新记录新光标
    ['keyup', 'focus', 'click'].forEach((key) => {
      node.removeEventListener(key, _editChangeCacheRange);
      node.addEventListener(key, _editChangeCacheRange);
    });

    const _pasteEvent = (e) => {
      e.preventDefault();
      let text = onPaste && onPaste(e);
      if (!text) {
        text = getPasteText(e);
        text = textFilters(html2text(text));
      }
      if (text) {
        // 创建内容模板元素
        const content = document.createElement('template');
        content.innerHTML = text;
        // 只要内容片段就行DocumentFragment
        insertNodeAtCaret(node, content.content);
      }
      return false;
    };

    // 拦截粘贴
    node.removeEventListener('paste', _pasteEvent);
    node.addEventListener('paste', _pasteEvent);

    // 监听节点
    observeNode(node, {
      onChange(mutationsList, observer) {
        // const datas = mutationsFilter(mutationsList, observer.target)
        // 空的时候保留一个<p><br><p>
        const isEmpty = !node.innerHTML;
        if (isEmpty) {
          node.innerHTML = '';
          const br = document.createElement('p');
          br.innerHTML = '<br>';
          insertNodeAtCaret(node, br);
        } else {
          // html2text 方法包含 trim() 去掉头尾的空白符，换行、空格、制表符等
          if (onChange) {
            const text = html2text(node.innerHTML);
            onChange(text);
          }
        }
        // 存光标这步很关键
        editCacheRange(node);
      },
    });

    /**
     * 插入元素到编辑器
     * @param {HTMLElement} insertNode  插入节点对象
     * @param {boolean} isEnter 是否是换行
     * @param {string} enterNodeName 换行标签,大写
     */
    const _insertNodeAtCaret = (insertNode, op = {}) => {
      return insertNodeAtCaret(node, insertNode, op);
    };

    const _insertHtml = (html) => {
      const _html = textFilters(html2text(html));
      return insertHtml(node, _html);
    };

    const _funcs = {
      ...funcs,
      insertNodeAtCaret: _insertNodeAtCaret,
      insertHtml: _insertHtml,
      html: _insertHtml,
      setMaxLength,
    };

    return _funcs;
  }

  return {
    ...funcs,
    initEdit,
  };
})();

const inEnter = document.querySelector('#inEnter');
const inImg = document.querySelector('#inImg');
const inText = document.querySelector('#inText');

const showMsg = document.querySelector('#showMsg');
const showText = document.querySelector('#showText');
const edit = document.querySelector('.edit');

let mgsValue = '输入点什么...������\n哈哈哈哈[猫猫]';

function setMsg(text) {
  showText.innerHTML = text;
  showMsg.innerHTML = simEditor.mgsFilters(text);
}

const editObj = simEditor.initEdit(edit, {
  onChange: (text) => {
    setMsg(text);
  },
  maxLength: 10,
});
// 初始化内容
editObj.html(mgsValue);

// editObj.setMaxLength(5)

inEnter.addEventListener('click', () => {
  const br = document.createElement('p');
  br.innerHTML = '<br>';
  // const br = document.createTextNode('\n')  // 纯\n换行
  // const br = document.createElement('br') // 纯br换行
  editObj.insertNodeAtCaret(br, { isEnter: true });
});

inText.addEventListener('click', () => {
  const text = document.createTextNode('哈哈');
  editObj.insertNodeAtCaret(text);
});

inImg.addEventListener('click', () => {
  const img = document.createElement('img');
  img.src = 'https://img.yzcdn.cn/vant/cat.jpeg';
  // img.src = ''
  img.alt = '[猫猫]';
  editObj.insertNodeAtCaret(img);
});
